package cn.ecnu.paper.service.impl;

import cn.ecnu.common.Constant;
import cn.ecnu.common.Permission;
import cn.ecnu.common.RedisConstant;
import cn.ecnu.exception.RRXException;
import cn.ecnu.paper.feign.SearchServiceFeign;
import cn.ecnu.paper.mapper.*;
import cn.ecnu.paper.pojo.*;
import cn.ecnu.paper.pojo.Collection;
import cn.ecnu.paper.service.PaperService;
import cn.ecnu.paper.service.PublisherService;
import cn.ecnu.paper.vo.*;
import cn.ecnu.utils.PaginationUtil;
import cn.ecnu.vo.common.Pagination;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class PaperServiceImpl implements PaperService {


    @Resource
    private PaperMapper paperMapper;

    @Resource
    private CollectionMapper collectionMapper;

    @Autowired
    private SearchServiceFeign searchFeign;

    @Resource
    private BelongMapper belongMapper;

    @Resource
    private ReferMapper referMapper;

    @Resource
    private WriteMapper writeMapper;

    @Resource
    private HistoryMapper historyMapper;

    @Autowired
    private RedisTemplate<Object, Object> objectRedisTemplate;

    @Autowired
    private ExecutorService executor;

    @Autowired
    private PublisherService publisherService;

    @Autowired
    private StringRedisTemplate redisTemplate;


    @Override
    public int insertPaper(PaperFrontVo vo) throws ExecutionException, InterruptedException {

        // 首先将paper插入到数据库
        CompletableFuture<Integer> paperTask = CompletableFuture.supplyAsync(() -> {
            // 首先插入paper
            Paper paper = new Paper();
            BeanUtils.copyProperties(vo, paper);
            paper.setFlag(1); // 设置论文可见
            paperMapper.insertPaper(paper);
            log.info("插入paper完成");
            return paper.getPaperId();
        }, executor);

        // 插入write
        CompletableFuture<Void> writeTask = paperTask.thenAcceptAsync((paperId) -> {
            if (vo.getAuthors() == null) {
                return; // 直接进行返回
            }
            vo.getAuthors().forEach((authorSort) -> {
                Write write = new Write();
                write.setPaperId(paperId);
                write.setAuthorId(authorSort.getAuthorId());
                write.setSort(authorSort.getSort());
                log.info("插入write完成");
                writeMapper.insertWrite(write);
            });
        }, executor);

        // 插入belong
        CompletableFuture<Void> belongTask = paperTask.thenAcceptAsync((paperId) -> {
            if (vo.getBelongs() == null) return;
            vo.getBelongs().forEach((fieldId -> {
                Belong belong = new Belong();
                belong.setPaperId(paperId);
                belong.setFieldId(fieldId);
                log.info("插入belong完成");
                belongMapper.insertBelong(belong);
            }));
        }, executor);

        // 插入refer
        CompletableFuture<Void> referTask = paperTask.thenAcceptAsync((paperId) -> {
            if (vo.getRefers() == null) return;
            vo.getRefers().forEach((referedId) -> {
                Refer refer = new Refer();
                refer.setRefererId(paperId);
                refer.setReferedId(referedId);
                log.info("插入refer完成");
                referMapper.insertRefer(refer);
            });
        }, executor);

        CompletableFuture<Void> all = CompletableFuture.allOf(writeTask, belongTask, referTask);
        all.get(); // 等待所有结果完成
        Integer paperId = paperTask.get();
        log.info("完成所有的任务");

        // 插入PaperModel
        CompletableFuture.runAsync(() -> {
            PaperVo paperVo = paperMapper.getPaperVoByPid(paperId);
            try {
                completePaperVo(paperVo);
            } catch (Exception e) {
                e.printStackTrace();
            }
            log.info("准备构造发送消息");
            publisherService.insertPaperModel(paperVo);
        }, executor);
        return 1;
    }

    // pageSize = 20
    @Override
    public ViewPaperVo getPapersByUid(Integer userId, Integer pageNum) throws ExecutionException, InterruptedException {
        ValueOperations<Object, Object> opsForValue = objectRedisTemplate.opsForValue();
        int pageSize = 10; // TODO: 页大小需要更改
        ViewPaperVo vo = new ViewPaperVo();

        Integer totalSize = paperMapper.getPapersCountByUid(userId);
        Pagination pagination = PaginationUtil.paging(pageNum, totalSize, pageSize, "/paper/getPapersByUserId/" + userId);
        vo.setPagination(pagination);
        pageNum = pagination.getCurrentPage();

        if (BooleanUtils.isTrue(objectRedisTemplate.hasKey(RedisConstant.PAPER_UID_PN + userId + ":" + pageNum))) {
            log.info(RedisConstant.PAPER_UID_PN + userId + ":" + pageNum + " " + " 缓存命中");
            return (ViewPaperVo) opsForValue.get(RedisConstant.PAPER_UID_PN + userId + ":" + pageNum);
        }

        // 首先获取每一个papers
        List<PaperVo> papers = paperMapper.getPaperVoByUid(userId, (pageNum - 1) * pageSize, pageSize);
        log.info("获取papers完成");

        // 接着组装每一个paper的author, refers, belongs, 遍历进行组装
        if (papers != null) {
            for (int i = 0; i < papers.size(); i++) {
                PaperVo paperVo = papers.get(i);
                completePaperVo(paperVo);
            }
        }
        vo.setPapers(papers);
        opsForValue.setIfAbsent(RedisConstant.PAPER_UID_PN + userId + ":" + pageNum, vo, 30, TimeUnit.MINUTES);
        return vo;
    }

    private void completePaperVo(PaperVo paperVo) throws ExecutionException, InterruptedException {
        Integer paperId = paperVo.getPaperId(); // 首先获取paperId

        // 为了防止并发冲突, 只允许一个人进行修改, 其余的人只返回结果
        // 获取author, 返回结果
        CompletableFuture<List<AuthorVoSort>> authorTask = CompletableFuture.supplyAsync(() -> {
            log.info("消息队列构造: 获取author成功");
            return writeMapper.getAuthorVo(paperId);
        }, executor);


        // 获取belongs, 返回结果
        CompletableFuture<List<BelongVo>> belongsTask = CompletableFuture.supplyAsync(() -> {
            log.info("消息队列构造: 获取belong成功");
            return belongMapper.getBelongs(paperId);
        }, executor);

        // 获取refers, 直接修改
        CompletableFuture<Void> refersTask = CompletableFuture.supplyAsync(() -> {
            return referMapper.getReferVo(paperId);
        }, executor).thenAccept(referVos -> {
            if (referVos != null) {
                for (int j = 0; j < referVos.size(); j++) {
                    ReferVo referVo = referVos.get(j);
                    referVo.setAuthors(writeMapper.getAuthorVo(referVo.getPaperId()));
                }
            }
            paperVo.setRefers(referVos);
            log.info("消息队列构造: 获取refers成功");
        });

        CompletableFuture<Void> all = CompletableFuture.allOf(authorTask, refersTask, belongsTask);
        all.get();
        paperVo.setBelongs(belongsTask.get());
        paperVo.setAuthors(authorTask.get());
        log.info("完成所有的消息构造");
    }

    @Override
    public int deletePaperById(Integer paperId, Integer userId, Integer permission) {
        Paper paper = paperMapper.getPaperById(paperId);
        // 如果paper不存在
        if (paper == null) throw new RRXException(Constant.NO_RESOURCE.getMsg(), Constant.NO_RESOURCE.getCode());
        // 权限不足
        if (NumberUtils.compare(userId, paper.getPosterId()) != 0 && !Permission.isAdmin(permission))
            throw new RRXException(Constant.NO_PERMISSION.getMsg(), Constant.NO_PERMISSION.getCode());
        // 最终删除

        // 调用ES删除
        CompletableFuture.runAsync(() -> {
            searchFeign.delete(paperId);
        });
        return paperMapper.deletePaperById(paperId);
    }

    @Override
    public PaperVo getPaperByPaperId(Integer paperId, Integer userId) throws ExecutionException, InterruptedException {
        PaperVo paperVo = paperMapper.getPaperVoByPid(paperId);
        if (paperVo == null) throw new RRXException(Constant.NO_RESOURCE.getMsg(), Constant.NO_RESOURCE.getCode());
        String title = paperVo.getTitle();
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            History history = new History(); history.setPid(paperId); history.setUid(userId); history.setDate(new Date());
            historyMapper.addHistory(history);
            SetOperations<String, String> opsForSet = redisTemplate.opsForSet();
            if (BooleanUtils.isNotTrue(opsForSet.isMember(RedisConstant.HOT_UNIQUE + paperId, userId.toString()))) {
                opsForSet.add(RedisConstant.HOT_UNIQUE + paperId, String.valueOf(userId));
                ZSetOperations<String, String> opsForZSet = redisTemplate.opsForZSet();
                opsForZSet.incrementScore(RedisConstant.HOT_RANK, title + ":" + paperId, 1);
                redisTemplate.expire(RedisConstant.HOT_RANK, Duration.ofHours(24 - LocalDateTime.now().getHour()));
                redisTemplate.expire(RedisConstant.HOT_UNIQUE + paperId, Duration.ofHours(24 - LocalDateTime.now().getHour()));
            }
        }, executor);
        ValueOperations<Object, Object> opsForValue = objectRedisTemplate.opsForValue();
        if (BooleanUtils.isTrue(objectRedisTemplate.hasKey(RedisConstant.PAPER_CPL_ID_CACHE + paperId))) {
            log.info(RedisConstant.PAPER_CPL_ID_CACHE + paperId + " " + " 缓存命中");
            return (PaperVo) opsForValue.get(RedisConstant.PAPER_CPL_ID_CACHE + paperId);
        }
        completePaperVo(paperVo);
        // 在热榜中更新点击次数
        // 在数据库中记录点击记录
        opsForValue.setIfAbsent(RedisConstant.PAPER_CPL_ID_CACHE + paperId, paperVo, 5, TimeUnit.MINUTES);
        return paperVo;
    }

    @Override
    public ViewPaperVo getPapersByAuthorId(Integer authorId, Integer pageNum) throws ExecutionException, InterruptedException {
        int pageSize = 10; // TODO: 这个页大小需要更改
        ViewPaperVo vo = new ViewPaperVo();

        int totalSize = paperMapper.getPapersCountByAuthorId(authorId);
        Pagination pagination = PaginationUtil.paging(pageNum, totalSize, pageSize, "/paper/getPapersByAuthorId/" + authorId);
        vo.setPagination(pagination);
        pageNum = pagination.getCurrentPage();
        ValueOperations<Object, Object> opsForValue = objectRedisTemplate.opsForValue();
        if (BooleanUtils.isTrue(objectRedisTemplate.hasKey(RedisConstant.PAPER_AUTHOR_PN_CACHE + authorId + ":" + pageNum))) {
            log.info(RedisConstant.PAPER_AUTHOR_PN_CACHE + authorId + ":" + pageNum + " " + " 缓存命中");
            return (ViewPaperVo) opsForValue.get(RedisConstant.PAPER_AUTHOR_PN_CACHE + authorId + ":" + pageNum);
        }


        // 首先获取每一个papers
        List<PaperVo> papers = paperMapper.getPaperVoByAuthorId(authorId, (pageNum - 1) * pageSize, pageSize);
        log.info("根据authorId获取papers完成");

        // 接着组装每一个paper的author, refers, belongs, 遍历进行组装
        if (papers != null) {
            for (int i = 0; i < papers.size(); i++) {
                PaperVo paperVo = papers.get(i);
                completePaperVo(paperVo);
            }
        }

        vo.setPapers(papers);
        opsForValue.setIfAbsent(RedisConstant.PAPER_AUTHOR_PN_CACHE + authorId + ":" + pageNum, vo, 5, TimeUnit.MINUTES);
        return vo;
    }


    @Override
    public ViewPaperVo getPapersByFieldId(Integer fieldId, Integer pageNum) throws ExecutionException, InterruptedException {
        int pageSize = 10; // TODO: 这个页大小需要更改
        ViewPaperVo vo = new ViewPaperVo();

        int totalSize = paperMapper.getPapersCountByFieldId(fieldId);
        Pagination pagination = PaginationUtil.paging(pageNum, totalSize, pageSize, "/paper/getPapersByFieldId/" + fieldId);
        vo.setPagination(pagination);
        pageNum = pagination.getCurrentPage();

        ValueOperations<Object, Object> opsForValue = objectRedisTemplate.opsForValue();
        if (BooleanUtils.isTrue(objectRedisTemplate.hasKey(RedisConstant.PAPER_FIELD_PN_CACHE + fieldId + ":" + pageNum))) {
            log.info(RedisConstant.PAPER_FIELD_PN_CACHE + fieldId + ":" + pageNum + " " + " 缓存命中");
            return (ViewPaperVo) opsForValue.get(RedisConstant.PAPER_FIELD_PN_CACHE + fieldId + ":" + pageNum);
        }

        // 首先获取每一个papers
        List<PaperVo> papers = paperMapper.getPaperVoByFiledId(fieldId, (pageNum - 1) * pageSize, pageSize);
        log.info("根据fieldId获取papers完成");

        // 接着组装每一个paper的author, refers, belongs, 遍历进行组装
        if (papers != null) {
            for (int i = 0; i < papers.size(); i++) {
                PaperVo paperVo = papers.get(i);
                completePaperVo(paperVo);
            }
        }

        vo.setPapers(papers);
        opsForValue.setIfAbsent(RedisConstant.PAPER_FIELD_PN_CACHE + fieldId + ":" + pageNum, vo, 5, TimeUnit.MINUTES);
        return vo;
    }

    @Override
    public int updatePaper(PaperFrontVo vo, Integer userId, Integer permission) throws ExecutionException, InterruptedException {
        Integer paperId = vo.getPaperId();
        Paper paper = paperMapper.getPaperById(paperId);
        // 如果paper不存在
        if (paper == null) throw new RRXException(Constant.NO_RESOURCE.getMsg(), Constant.NO_RESOURCE.getCode());
        // 权限不足
        if (NumberUtils.compare(userId, paper.getPosterId()) != 0 && !Permission.isAdmin(permission))
            throw new RRXException(Constant.NO_PERMISSION.getMsg(), Constant.NO_PERMISSION.getCode());
        // 进行修改
        // 首先将paper更新到数据库
        CompletableFuture<Void> paperTask = CompletableFuture.runAsync(() -> {
            // 首先更新paper
            BeanUtils.copyProperties(vo, paper);
            paper.setFlag(1); // 设置论文可见
            paperMapper.updatePaper(paper);
            log.info("更新paper完成");
        }, executor);

        // 更新write
        CompletableFuture<Void> writeTask = CompletableFuture.runAsync(() -> {
            // 删除原来的authors
            writeMapper.deleteWriteByPaperId(paperId);

            // 插入新的Author
            if (vo.getAuthors() == null) {
                return; // 直接进行返回
            }
            vo.getAuthors().forEach((authorSort) -> {
                Write write = new Write();
                write.setPaperId(paperId);
                write.setAuthorId(authorSort.getAuthorId());
                write.setSort(authorSort.getSort());
                log.info("插入write完成");
                writeMapper.insertWrite(write);
            });
        }, executor);

        // 更新belong
        CompletableFuture<Void> belongTask = CompletableFuture.runAsync(() -> {
            // 首先删除belong
            belongMapper.deleteBelongByPaperId(paperId);
            // 插入新的belongs
            if (vo.getBelongs() == null) return;
            vo.getBelongs().forEach((fieldId -> {
                Belong belong = new Belong();
                belong.setPaperId(paperId);
                belong.setFieldId(fieldId);
                log.info("更新belong完成");
                belongMapper.insertBelong(belong);
            }));
        }, executor);

        // 更新refer
        CompletableFuture<Void> referTask = CompletableFuture.runAsync(() -> {
            // 首先删除所有的refer
            referMapper.deleteReferByRefererId(paperId);
            // 新增所有的refer
            if (vo.getRefers() == null) return;
            vo.getRefers().forEach((referedId) -> {
                Refer refer = new Refer();
                refer.setRefererId(paperId);
                refer.setReferedId(referedId);
                log.info("更新refer完成");
                referMapper.insertRefer(refer);
            });
        }, executor);

//        CompletableFuture<Void> all = CompletableFuture.allOf(writeTask, belongTask, referTask);
//        all.get(); // 不需要等待所有的结果完成, 直接后台运行就行了
        log.info("完成所有的更新任务");

        // Paper插入到ElasticSearch中
        CompletableFuture.runAsync(() -> {
            PaperVo paperVo = paperMapper.getPaperVoByPid(paperId);
            try {
                completePaperVo(paperVo);
            } catch (Exception e) {
                e.printStackTrace();
            }
            publisherService.insertPaperModel(paperVo);
        });
        return 1;
    }

    @Override
    public int collectPaper(CollectVo vo, Integer userId) {
        if (NumberUtils.compare(vo.getUserId(), userId) != 0)
            throw new RRXException(Constant.NO_PERMISSION.getMsg(), Constant.NO_PERMISSION.getCode());
        return collectionMapper.addClt(new Collection(userId, vo.getPaperId(), new Date(), 1));
    }

    @Override
    public int uncollect(CollectVo vo, Integer userId) {
        if (NumberUtils.compare(vo.getUserId(), userId) != 0)
            throw new RRXException(Constant.NO_PERMISSION.getMsg(), Constant.NO_PERMISSION.getCode());
        return collectionMapper.unCollect(userId, vo.getPaperId());
    }

    @Override
    public ViewPaperVo getCollectionByUid(Integer uid, Integer pageNum) throws ExecutionException, InterruptedException {
        int pageSize = 10; // TODO: 页大小需要更改
        ViewPaperVo vo = new ViewPaperVo();

        Integer totalSize = collectionMapper.getCollectionCountByUid(uid);
        Pagination pagination = PaginationUtil.paging(pageNum, totalSize, pageSize, "/paper/collection/" + uid);
        vo.setPagination(pagination);
        pageNum = pagination.getCurrentPage();
        // 首先获取每一个papers
        List<PaperVo> papers = paperMapper.getCollectionByUid(uid, (pageNum - 1) * pageSize, pageSize);
        log.info("获取papers完成");

        // 接着组装每一个paper的author, refers, belongs, 遍历进行组装
        if (papers != null) {
            for (int i = 0; i < papers.size(); i++) {
                PaperVo paperVo = papers.get(i);
                completePaperVo(paperVo);
            }
        }
        vo.setPapers(papers);
        return vo;
    }

    @Override
    public HotVo getHotPaper() throws ExecutionException, InterruptedException {
        ValueOperations<Object, Object> opsForValue = objectRedisTemplate.opsForValue();
        if (BooleanUtils.isTrue(objectRedisTemplate.hasKey(RedisConstant.PAPER_HOT_CACHE))) {
            log.info(RedisConstant.PAPER_HOT_CACHE + " " + " 缓存命中");
            return (HotVo) opsForValue.get(RedisConstant.PAPER_HOT_CACHE);
        }

        ZSetOperations<String, String> opsForZSet = redisTemplate.opsForZSet();
        Set<ZSetOperations.TypedTuple<String>> tuples = opsForZSet.reverseRangeByScoreWithScores(RedisConstant.HOT_RANK, 0, Integer.MAX_VALUE);
        if (tuples == null) return null;
        List<PaperVo> papers = new ArrayList<>();
        List<Double> hots = tuples.stream().map((tuple) -> {
            String[] titleAndId = tuple.getValue().split(":");
            PaperVo paperVo = paperMapper.getPaperVoByPid(Integer.valueOf(titleAndId[1]));
//            System.out.println(tuple.getValue()); // TODO:
            papers.add(paperVo);
            return tuple.getScore();
        }).limit(50).collect(Collectors.toList());

        for (PaperVo paperVo : papers) {
            completePaperVo(paperVo);
        }
        HotVo hotVo = new HotVo();
        hotVo.setHot(hots); hotVo.setPapers(papers);
        // TODO: 部署时更改
        opsForValue.setIfAbsent(RedisConstant.PAPER_HOT_CACHE, hotVo, 5, TimeUnit.MINUTES);
        return hotVo;
    }

    @Override
    public VisualData getVisualizationData(Integer uid) {
        VisualData data = new VisualData();
        data.setTotal(paperMapper.getPapersCountByUid(uid));
        data.setFields(paperMapper.getPaperFieldByUid(uid));
        data.setUserId(uid);
        data.setDates(paperMapper.getPaperDateByUid(uid));
        return data;
    }

    @Override
    public VisualData getAllVisualizationData() {
        VisualData data = new VisualData();
        data.setTotal(paperMapper.getPaperCount());
        data.setFields(paperMapper.getAllPaperField());
        data.setDirections(paperMapper.getAllPaperDirection());
        return data;
    }

    @Override
    public ViewPaperVo getPaperList(Integer pageNum) throws ExecutionException, InterruptedException {
        int pageSize = 10; // TODO: 这个页大小需要更改
        ViewPaperVo vo = new ViewPaperVo();

        int totalSize = paperMapper.getPaperCount();
        Pagination pagination = PaginationUtil.paging(pageNum, totalSize, pageSize, "/paper/list");
        vo.setPagination(pagination);
        pageNum = pagination.getCurrentPage();

        ValueOperations<Object, Object> opsForValue = objectRedisTemplate.opsForValue();
        if (BooleanUtils.isTrue(objectRedisTemplate.hasKey(RedisConstant.PAPER_ALL_CACHE + pageNum))) {
            log.info(RedisConstant.PAPER_ALL_CACHE + pageNum + " " + " 缓存命中");
            return (ViewPaperVo) opsForValue.get(RedisConstant.PAPER_ALL_CACHE  + pageNum);
        }

        // 首先获取每一个papers
        List<PaperVo> papers = paperMapper.getPaperList((pageNum - 1) * pageSize, pageSize);
        log.info("获取所有papers完成");

        // 接着组装每一个paper的author, refers, belongs, 遍历进行组装
        if (papers != null) {
            for (int i = 0; i < papers.size(); i++) {
                PaperVo paperVo = papers.get(i);
                completePaperVo(paperVo);
            }
        }

        vo.setPapers(papers);
        opsForValue.setIfAbsent(RedisConstant.PAPER_ALL_CACHE + pageNum, vo, 5, TimeUnit.MINUTES);
        return vo;
    }

    @Override
    public ViewPaperVo getPaperAll() throws ExecutionException, InterruptedException {
        ViewPaperVo vo = new ViewPaperVo();

        ValueOperations<Object, Object> opsForValue = objectRedisTemplate.opsForValue();
        if (BooleanUtils.isTrue(objectRedisTemplate.hasKey(RedisConstant.PAPER_ALL_CACHE + "all"))) {
            log.info(RedisConstant.PAPER_ALL_CACHE + "all" + " " + " 缓存命中");
            return (ViewPaperVo) opsForValue.get(RedisConstant.PAPER_ALL_CACHE  + "all");
        }

        // 首先获取每一个papers
        List<PaperVo> papers = paperMapper.getPaperALL();
        System.out.println(papers);
        log.info("获取所有papers完成");

        // 接着组装每一个paper的author, refers, belongs, 遍历进行组装
        if (papers != null) {
            for (int i = 0; i < papers.size(); i++) {
                PaperVo paperVo = papers.get(i);
                completePaperVo(paperVo);
            }
        }

        vo.setPapers(papers);
        opsForValue.setIfAbsent(RedisConstant.PAPER_ALL_CACHE + "all", vo, 5, TimeUnit.MINUTES);
        return vo;
    }

    @Override
    public List<PaperVo> getPaperByIds(List<Integer> ids) throws ExecutionException, InterruptedException {
        List<PaperVo> vos = ids.stream().map((id) -> {
            return paperMapper.getPaperVoByPid(id);
        }).collect(Collectors.toList());
        for (PaperVo vo: vos) {
            completePaperVo(vo);
        }
        return vos;
    }
}
